﻿using System;
using System.Linq;
using System.Drawing;
using WiMo.Games.Drawables;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using TestOpenGL.OpenGLES;


namespace WiMo.Games.OpenGLDisplay
{
    public class DisplayManager : IDisplay
    {
        #region Boring "stuff"
        [DllImport("coredll", EntryPoint = "GetDC", SetLastError = true)]
        private static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("coredll.dll", EntryPoint = "ReleaseDC", SetLastError = true)]
        private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

        [DllImport("DirectDrawWrapperUnmanaged.dll", CallingConvention = CallingConvention.Winapi)]
        public static unsafe extern uint LoopIt(uint itterations);

        IntPtr _hwnd;
        Bitmap _buffer;
        List<IDrawable> _items;
        List<IDrawable> _previousItems;
        Size _originalSize;
        public bool Diagnostics { get; set; }
        public DisplayStrategies DisplayStrategy { get; set; }
        int _currentZIndex;

        private Color _backgroundColor;
        public Color BackgroundColor
        {
            get { return _backgroundColor; }
            set
            {
                if (_backgroundColor != value)
                {
                    Invalidate();
                    _backgroundColor = value;
                }
            }
        }
        public ITexture _backgroundImage;
        public ITexture BackgroundImage
        {
            get { return _backgroundImage; }
            set
            {
                if (_backgroundImage != value)
                {
                    _backgroundImage = value;
                    Invalidate();
                }
            }
        }
        public int DPI { get; set; }
        public Vector2 ScaleFactor { get; private set; }
        public byte AspectRatio { get; set; }
        public Location DisplayOffset { get; private set; }
        public Size Size { get; private set; }
        public Size TargetSize { get; private set; }
        private RenderTree _renderTree;
        private bool _isSceneDirty = true;

        EGLDisplay _display;
        EGLSurface _surface;
        EGLContext _context;

        OpenGL.Test _test;

        private void SetDPI()
        {
            var bitmap = new Bitmap(64, 64);
            var gr = Graphics.FromImage(bitmap);
            DPI = Convert.ToInt32(gr.DpiX);
            gr.Dispose();
            bitmap.Dispose();
        }

        private int _majorGLVersion, _minorGLVersion;

        #region DisplayManager
        public DisplayManager(IntPtr hwnd, Size targetSize, Size size, byte aspectRatio)
        {
            SetDPI();
            Size = size;
            _originalSize = size;

            _test = new WiMo.Games.OpenGLDisplay.OpenGL.Test();            

            _hwnd = hwnd;
            BackgroundColor = Color.Black;
            AspectRatio = aspectRatio;
            Size = size;
            TargetSize = targetSize;
            var widthRatio = Convert.ToSingle(size.Width) / Convert.ToSingle(targetSize.Width);
            var heightRatio = Convert.ToSingle(size.Height) / Convert.ToSingle(targetSize.Height);

            _renderTree = RenderTree.Create(size, 3);

            switch (aspectRatio)
            {
                case WiMo.Games.Display.AspetRatioOneByOne:
                case WiMo.Games.Display.AspetRatioFourByThree:
                case WiMo.Games.Display.AspetRatioThreeByFour:
                    var ratio = Math.Min(heightRatio, widthRatio);
                    ScaleFactor = new Vector2(ratio, ratio);

                    if (widthRatio != heightRatio)
                    {
                        if (widthRatio < heightRatio)
                            DisplayOffset = new Location(0, (size.Height - (targetSize.Height * ratio)) / 2);
                        else
                            DisplayOffset = new Location((size.Width - (targetSize.Width * ratio)) / 2, 0);
                    }
                    break;
                default:
                    ScaleFactor = new Vector2(widthRatio, heightRatio);
                    break;
            }

            _display = egl.GetDisplay(new EGLNativeDisplayType(hwnd));

            egl.Initialize(_display, out _majorGLVersion, out _minorGLVersion);

            EGLConfig[] configs = new EGLConfig[10];
            int[] attribList = new int[] 
            { 
                egl.EGL_RED_SIZE, 5, 
                egl.EGL_GREEN_SIZE, 6, 
                egl.EGL_BLUE_SIZE, 5, 
                egl.EGL_DEPTH_SIZE, 16 , 
                egl.EGL_SURFACE_TYPE, egl.EGL_WINDOW_BIT,
                egl.EGL_STENCIL_SIZE, egl.EGL_DONT_CARE,
                egl.EGL_NONE, egl.EGL_NONE 
            };

            int numConfig;
            if (!egl.ChooseConfig(_display, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
                throw new InvalidOperationException("Unable to choose config.");

            EGLConfig config = configs[0];
            _surface = egl.CreateWindowSurface(_display, config,_hwnd, null);
            _context = egl.CreateContext(_display, config, EGLContext.None, null);
            egl.MakeCurrent(_display, _surface, _surface, _context);

            InitGL();

            _test.Init();

            DisplayStrategy = DisplayStrategies.Automatic;
        }

        void InitGL()
        {
            gl.ShadeModel(gl.GL_SMOOTH);
            gl.ClearColor(0.8f, 0.8f, 0.8f, 1.0f);
            gl.ClearDepthf(1.0f);
            gl.DepthFunc(gl.GL_LEQUAL);
            gl.AlphaFunc(gl.GL_GREATER, 0.5f);
            gl.Enable(gl.GL_DEPTH_TEST);
        }
        #endregion

        public string Name
        {
            get { return string.Format("OpenGL {0}.{1}", _majorGLVersion, _minorGLVersion); }
        }

        public void Reset()
        {
            _previousItems = _items;
            _items = new List<IDrawable>();
        }
        #endregion

        public void Add(IDrawable drawable)
        {
            //_renderTree.AddSprite(drawable);
            drawable.ZIndex = _currentZIndex;
            _currentZIndex++;
            _items.Add(drawable);
        }

        #region Premium render engine does not currently support adding items to the screen ad-hoc
        public ISpriteLine DrawLine(Location start, Location end, Color color, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteText DrawText(PointF position, string message, ISpriteFont font, Color foreColor)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteText DrawText(PointF position, string message, ISpriteFont font, Color foreColor, Origin origin)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape DrawRectangle(Rectangle rect, Color color, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape DrawRectangle(Rectangle rect, Color color, int width, Origin origin)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape DrawCircle(PointF position, int radius, Color borderColor, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape FillRectangle(Rectangle rect, Color fillColor, Color borderColor, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape FillRectangle(Rectangle rect, Color fillColor, Color borderColor, int width, Origin origin)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }

        public ISpriteShape FillCircle(PointF position, int radius, Color fillColor, Color borderColor, int width)
        {
            throw new NotImplementedException("Premium render engine does not support doing ad-hoc drawing, use asset factory to create instance of objects you need to render.");
        }
        #endregion

        #region Utility Methods
        private Point GetOrigin(Origin origin, PointF original, Size size)
        {
            var point = new Point();

            if (origin == Origin.TopLeft)
                point = new Point(Convert.ToInt32(original.X), Convert.ToInt32(original.Y));
            else
                point = new Point(Convert.ToInt32(original.X - size.Width / 2), Convert.ToInt32(original.Y - size.Height / 2));

            return point;

            //            return new Point(Convert.ToInt32(point.X * ScaleFactor.X), Convert.ToInt32(point.Y * ScaleFactor.Y));
        }

        private Size GetSize(Size size)
        {
            return size;
            //return new Size(size.Width * ScaleFactor.X, size.Height * ScaleFactor.Y);
        }

        private Point[] GetPointArray(List<Location> locations, Point offset)
        {
            Point[] points = new Point[locations.Count];
            for (int i = 0; i < locations.Count; i++)
                points[i] = new Point(locations[i].X + offset.X, locations[i].Y + offset.Y);

            return points;
        }
        #endregion

        private void RenderBackground()
        {

        }

        float _rotation = 0.0f;

        #region Render (Main)
        public void Render()
        {
            _isSceneDirty = true;

            var allTimer = System.Diagnostics.Stopwatch.StartNew();

            var timer = System.Diagnostics.Stopwatch.StartNew();
            timer.Start();

            RenderBackground();

            var fillTime = timer.ElapsedMilliseconds;

            /*if (_previousItems != null)
            {
                foreach (var item in _previousItems)
                {
                    if(!_items.Contains(item))
                        _renderTree.RemoveSprite(item);
                }
            }*/

            timer.Reset();
            timer.Start();

            gl.Viewport(0, 0, Size.Width, Size.Height);
            gl.MatrixMode(gl.GL_PROJECTION);
            gl.LoadIdentity();
            gl.Orthof(-1, 1, -1, 1, -10, 10);

            gl.MatrixMode(gl.GL_MODELVIEW);
            gl.LoadIdentity();

            _rotation += 2f;
            if (_rotation > 359.0f) _rotation = 0.0f;
            _test.Draw(_rotation);

            gl.MatrixMode(gl.GL_PROJECTION);
            gl.LoadIdentity();
            gl.Orthof(0, Size.Width, Size.Height, 0, -10, 10);

            gl.MatrixMode(gl.GL_MODELVIEW);
            gl.LoadIdentity();
           
            gl.Translatef(10, Size.Height - 35, 0);



            var dirtyRects = 0;
            long listTime = 0;
            //if (_isSceneDirty)
            //{
            //    //_deviceContext = _graphics.GetHdc();

            //    foreach (var sprite in _items)
            //        sprite.Render(this);

            //    //_graphics.ReleaseHdc(_deviceContext);
            //}
            //else
            //{

            //    dirtyRects = _renderTree.DirtyRenderTargets.Count;
            //}

            listTime = timer.ElapsedMilliseconds;
            timer.Reset();
            timer.Start();

            egl.SwapBuffers(_display, _surface);
            
            _isSceneDirty = false;
            _renderTree.MarkAsClean();

            //#region Code to render the graphic to the device.
            //var dc = GetDC(_hwnd);
            //var gr = Graphics.FromHdc(dc);
            //gr.DrawImage(_buffer, 0, 0);
            //gr.Dispose();
            //ReleaseDC(_hwnd, dc);
            //#endregion

            var flipTime = timer.ElapsedMilliseconds;

            gl.Clear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
            System.Diagnostics.Debug.WriteLine(string.Format("Total: {0:000} Render Background: {1:000} List {2:000} Flip Time {3:000} Dirty Rects {4}", allTimer.ElapsedMilliseconds, fillTime, listTime, flipTime, dirtyRects));
        }
        #endregion

        #region Render Image
        public void Render(ISpriteImage sprite)
        {

        }
        #endregion

        #region Render Font
        private Point GetAdjustedPosition(IPositionable shape)
        {
            int left = shape.Position.Point.X;
            int top = shape.Position.Point.Y;
            if (shape.Origin == Origin.Center)
            {
                left = Convert.ToInt32(left - shape.Size.Width / 2);
                top = Convert.ToInt32(top - shape.Size.Height / 2);
            }

            left += DisplayOffset.X;
            top += DisplayOffset.Y;

            return new Point(left, top);
        }

        public void Render(ISpriteText text)
        {

        }
        #endregion

        #region Render Shape
        public void Render(ISpriteShape shape)
        {
            var borderPen = new Pen(shape.BorderColor.ToDrawingColor(), Convert.ToSingle(shape.BorderWidth));
            var fillBrush = shape.FillColor != null ? new SolidBrush(shape.FillColor.ToDrawingColor()) : null;

            var point = GetOrigin(shape.Origin, shape.Position, shape.Size);

            switch (shape.ShapeType)
            {
                case ShapeType.Ellipse:
                    //if (fillBrush != null)
                    //    _graphics.FillEllipse(fillBrush, point.X, point.Y, shape.Size.Width, shape.Size.Height);

                    //if (borderPen != null)
                    //    _graphics.DrawEllipse(borderPen, point.X, point.Y, shape.Size.Width, shape.Size.Height);

                    break;
                case ShapeType.Rectangle:
                    //var position = GetAdjustedPosition(shape);
                    //_graphics.DrawImage(shape.DrawInformation as Bitmap, position.X, position.Y);
                    break;
                case ShapeType.LineList:
                    //if (borderPen != null)
                    //    _graphics.DrawLines(borderPen, GetPointArray(shape.Points, point));

                    break;
                case ShapeType.Polygon:
                    //if (fillBrush != null)
                    //    _graphics.FillPolygon(fillBrush, GetPointArray(shape.Points, point));

                    //if (borderPen != null)
                    //    _graphics.DrawLines(borderPen, GetPointArray(shape.Points, point));
                    break;
            }

            if (fillBrush != null)
                fillBrush.Dispose();

            if (borderPen != null)
                borderPen.Dispose();
        }
        #endregion

        #region Render Line
        public void Render(ISpriteLine line)
        {
            //var pen = new Pen(line.LineColor.ToDrawingColor(), Convert.ToSingle(line.Width));
            //_graphics.DrawLine(pen, line.Start.X, line.Start.Y, line.End.X, line.End.Y);
            //pen.Dispose();
        }
        #endregion

        public void Invalidate()
        {
            _isSceneDirty = true;
        }

        #region Resize
        public void Resize(Size bounds)
        {

        }
        #endregion

        public ISpriteImage CreateSpriteImage()
        {
            return new Drawables.SpriteImage();
        }

        public ISpriteShape CreateRectangle()
        {
            return new Drawables.Rectangle();
        }

        public ISpriteText CreateText()
        {
            return new Drawables.SpriteText();
        }

        public ISpriteLine CreateLine()
        {
            return new Drawables.Line();
        }

        public ISpriteShape CreateEllipse()
        {
            return new Drawables.Ellipse();
        }

        public ISpriteShape CreatePolygon()
        {
            return new Drawables.Polygon();
        }

        public ISpriteShape CreateLineList()
        {
            return new Drawables.LineList();
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (!egl.DestroySurface(_display, _surface))
                throw new Exception("Error while destroying surface.");
            if (!egl.DestroyContext(_display, _context))
                throw new Exception("Error while destroying context.");
            if (!egl.Terminate(_display))
                throw new Exception("Error while terminating display.");

        }

        #endregion
    }
}
